/***********************************************************
     Starter code for Assignment 3

     This code was originally written by Jack Wang for
		    CSC418, SPRING 2005

		implements light_source.h

***********************************************************/

#include <cmath>
#include "light_source.h"
#include "../Materials/Material.h"
#include "../Shapes/scene_object.h"
#include "../ISceneNavigator.h"

bool LightSource::isInShadow(const Point3D& point,
                             ISceneNavigator& sceneNavigator)
{
    Ray3D lightSourceRay;
    lightSourceRay.dir = get_position() - point;
    double eps = 0.01;
    lightSourceRay.origin = point + eps * lightSourceRay.dir;
    double distanceToLight = lightSourceRay.dir.length();
    lightSourceRay.dir.normalize();
    
    sceneNavigator.navigateScene(lightSourceRay);
    
    // If there is no intersection with any object as we approach light from this point
    // or if the intersection happens after the light source, then point is in direct
    // source of light
    if ((lightSourceRay.intersection.none) ||
        (distanceToLight <= (lightSourceRay.intersection.point - lightSourceRay.origin).length()))
    {
        return false;
    }
    
    return true;
}

Colour LightSource::shadeShadow(Ray3D& ray, ISceneNavigator& sceneNavigator, bool isInShadow)
{
    return isInShadow ? Colour() : ray.col;
}

// Computes a single diffuse color
// NOTE: All normals must be normalized
Colour ComputeDiffuseColor(Colour surfaceReflectance,
                           Colour colorIntensity,
                           Vector3D lightDirection,
                           Vector3D normalToSurface)
{
    return lightDirection.dot(normalToSurface) * surfaceReflectance * colorIntensity;
}

// Computes a single specular color
// NOTE: All normals must be normalized
Colour ComputeSpecularColor(Colour surfaceReflectance,
                            Colour colorIntensity,
                            Vector3D lightDirection,
                            Vector3D normalToSurface,
                            Vector3D cameraDirection,
                            double highlightWidth)
{
    Vector3D m = 2 * (normalToSurface.dot(lightDirection)) * normalToSurface - lightDirection;
    m.normalize();
    return pow(fmax(0, m.dot(cameraDirection)), highlightWidth) * surfaceReflectance * colorIntensity;
}

void PointLight::enableSpecularShading(bool value)
{
    _enableSpecularShading = value;
}

Colour PointLight::getAmbientColour() const
{
    return _col_ambient;
}

void PointLight::shade(Ray3D& ray)
{	
    Vector3D lightDirection = get_position() - ray.intersection.point;
    lightDirection.normalize();
    
    Vector3D n = ray.intersection.normal;
    n.normalize();
    
    Vector3D cameraDirection = -ray.dir;
    cameraDirection.normalize();
    
    ray.col = ray.col + ComputeDiffuseColor(ray.intersection.obj->getMaterial()->getColour(Diffuse, ray.intersection, false),
                                            _col_diffuse,
                                            lightDirection,
                                            ray.intersection.normal);
    
    if (_enableSpecularShading)
    {
        ray.col = ray.col + ComputeSpecularColor(ray.intersection.obj->getMaterial()->getColour(Specular, ray.intersection, false),
                                                 _col_specular,
                                                 lightDirection,
                                                 ray.intersection.normal,
                                                 cameraDirection,
                                                 ray.intersection.obj->getMaterial()->getSpecularExp());
    }
    
    ray.col.clamp();
}
